EXP_GROWTH

Overview

The EXP_GROWTH function fits a variety of exponential growth models to x-y data using nonlinear least squares regression. Exponential growth describes processes where the rate of change is proportional to the current value, making these models essential for analyzing population dynamics, compound interest, radioactive decay, and biological growth phenomena.

This implementation uses scipy.optimize.curve_fit from the SciPy library, which employs the Levenberg-Marquardt algorithm for unconstrained problems or Trust Region Reflective (TRF) methods when parameter bounds are specified. The function minimizes the sum of squared residuals between the observed data and the model predictions.

The function supports ten distinct exponential growth models:

  • Standard exponential growth: y = a \cdot e^{bx} — the classic unbounded growth model
  • Geometric growth: y = a \cdot b^x — discrete-time exponential growth with base b
  • Asymptotic saturation: y = 1 - e^{-Ax} — growth approaching a maximum limit
  • Power base saturation: y = 1 - B^x — saturation with power-law base decay
  • Log-linear form: y = e^{a + bx} — exponential model expressed in log-linear terms
  • Single-phase growth: y = y_0 + A_1 \cdot e^{x/t_1} — growth with baseline offset
  • Dual-phase growth: combines two exponential terms with different time constants
  • Triple-phase growth: three-component exponential for complex growth dynamics
  • Biphasic association: sum of two saturation terms for multi-stage processes
  • Delayed onset growth: exponential growth starting after a threshold x_0

For each model, the function returns the fitted parameter values and, when covariance estimation succeeds, the standard errors calculated as the square root of the diagonal elements of the covariance matrix. These standard errors provide uncertainty estimates derived from a linear approximation to the model at the optimum.

This example function is provided as-is without any representation of accuracy.

Excel Usage

=EXP_GROWTH(xdata, ydata, exp_growth_model)
  • xdata (list[list], required): The xdata value
  • ydata (list[list], required): The ydata value
  • exp_growth_model (str, required): The exp_growth_model value

Returns (list[list]): A 2D list where the first row contains parameter names, the second row contains fitted parameter values, and an optional third row contains standard errors (when covariance estimation succeeds). Returns an error string on failure.

Examples

Example 1: Demo case 1

Inputs:

exp_growth_model xdata ydata
exp_asymptotic_saturation 0.01 0.03678022283335006
2.0075 0.9933091358855604
4.005 1.012573003679107
6.0024999999999995 1.0296038987500231
8 0.9954486321684086

Excel formula:

=EXP_GROWTH("exp_asymptotic_saturation", {0.01;2.0075;4.005;6.0024999999999995;8}, {0.03678022283335006;0.9933091358855604;1.012573003679107;1.0296038987500231;0.9954486321684086})

Expected output:

A
3.664
1.699

Example 2: Demo case 2

Inputs:

exp_growth_model xdata ydata
exp_power_base_saturation 0.1 0.06610565381268688
1.3250000000000002 0.5448752512710233
2.5500000000000003 0.7927946466234451
3.7750000000000004 0.9200750011371784
5 0.9458654821398577

Excel formula:

=EXP_GROWTH("exp_power_base_saturation", {0.1;1.3250000000000002;2.5500000000000003;3.7750000000000004;5}, {0.06610565381268688;0.5448752512710233;0.7927946466234451;0.9200750011371784;0.9458654821398577})

Expected output:

B
0.5414
0.007212

Example 3: Demo case 3

Inputs:

exp_growth_model xdata ydata
exp_geometric_growth_base 0.1 2.7700051177663165
1.3250000000000002 2.9318268135473864
2.5500000000000003 3.1228829896653267
3.7750000000000004 3.3262471742631257
5 3.5066842214375136

Excel formula:

=EXP_GROWTH("exp_geometric_growth_base", {0.1;1.3250000000000002;2.5500000000000003;3.7750000000000004;5}, {2.7700051177663165;2.9318268135473864;3.1228829896653267;3.3262471742631257;3.5066842214375136})

Expected output:

a b
2.756 1.05
0.008175 0.0009384

Example 4: Demo case 4

Inputs:

exp_growth_model xdata ydata
exp_growth_standard 0.01 120.83587509019658
2.0075 0.01
4.005 338.2925056679918
6.0024999999999995 1863.496122144786
8 12173.781005925137

Excel formula:

=EXP_GROWTH("exp_growth_standard", {0.01;2.0075;4.005;6.0024999999999995;8}, {120.83587509019658;0.01;338.2925056679918;1863.496122144786;12173.781005925137})

Expected output:

a b
6.818 0.9359
1.067 0.01967

Example 5: Demo case 5

Inputs:

exp_growth_model xdata ydata
exp_log_linear_form 0.01 687.342222705341
2.0075 0.01
4.005 1924.2855037614677
6.0024999999999995 10599.99412957201
8 69247.2636052396

Excel formula:

=EXP_GROWTH("exp_log_linear_form", {0.01;2.0075;4.005;6.0024999999999995;8}, {687.342222705341;0.01;1924.2855037614677;10599.99412957201;69247.2636052396})

Expected output:

a b
3.658 0.9359
0.1566 0.01968

Example 6: Demo case 6

Inputs:

exp_growth_model xdata ydata
exp_biphasic_association 0.01 0.049587290376612685
2.0075 1.7933284068008737
4.005 3.061975136237548
6.0024999999999995 3.9431458463858697
8 4.3535914439026975

Excel formula:

=EXP_GROWTH("exp_biphasic_association", {0.01;2.0075;4.005;6.0024999999999995;8}, {0.049587290376612685;1.7933284068008737;3.061975136237548;3.9431458463858697;4.3535914439026975})

Expected output:

y0 A1 t1 A2 t2
0.01992 1.747 4.947 3.724 4.947

Example 7: Demo case 7

Inputs:

exp_growth_model xdata ydata
exp_single_phase_growth 0.01 2.8499898323607833
2.0075 4.066060082367748
4.005 6.201240348543285
6.0024999999999995 9.316828126451941
8 13.36219754582273

Excel formula:

=EXP_GROWTH("exp_single_phase_growth", {0.01;2.0075;4.005;6.0024999999999995;8}, {2.8499898323607833;4.066060082367748;6.201240348543285;9.316828126451941;13.36219754582273})

Expected output:

y0 A1 t1
-0.5557 3.309 5.557
0.6704 0.5714 0.5013

Example 8: Demo case 8

Inputs:

exp_growth_model xdata ydata
exp_dual_phase_growth 0 21
0.5 28.433751
1 40.175443
1.5 58.96562
2 89.316983
2.5 138.663431
3 219.254438
3.5 351.278294
4 568.011369

Excel formula:

=EXP_GROWTH("exp_dual_phase_growth", {0;0.5;1;1.5;2;2.5;3;3.5;4}, {21;28.433751;40.175443;58.96562;89.316983;138.663431;219.254438;351.278294;568.011369})

Expected output:

y0 A1 t1 A2 t2
2 10 1 9 5
0.00002545 0.000001007 1.667e-8 0.00002438 0.00001477

Example 9: Demo case 9

Inputs:

exp_growth_model xdata ydata
exp_triple_phase_growth 0 1.4
0.5 2.208792
1 4.229361
1.5 9.499059
2 23.544291
2.5 61.372562
3 163.75968

Excel formula:

=EXP_GROWTH("exp_triple_phase_growth", {0;0.5;1;1.5;2;2.5;3}, {1.4;2.208792;4.229361;9.499059;23.544291;61.372562;163.75968})

Expected output:

"non-error"

Example 10: Demo case 10

Inputs:

exp_growth_model xdata ydata
exp_delayed_onset_growth 0.01 2.8499898323607833
2.0075 4.066060082367748
4.005 6.201240348543285
6.0024999999999995 9.316828126451941
8 13.36219754582273

Excel formula:

=EXP_GROWTH("exp_delayed_onset_growth", {0.01;2.0075;4.005;6.0024999999999995;8}, {2.8499898323607833;4.066060082367748;6.201240348543285;9.316828126451941;13.36219754582273})

Expected output:

"non-error"

Python Code

import numpy as np
from scipy.optimize import curve_fit as scipy_curve_fit
import math

def exp_growth(xdata, ydata, exp_growth_model):
    """
    Fits exponential growth models to data using scipy.optimize.curve_fit.

    See: https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.curve_fit.html

    This example function is provided as-is without any representation of accuracy.

    Args:
        xdata (list[list]): The xdata value
        ydata (list[list]): The ydata value
        exp_growth_model (str): The exp_growth_model value Valid options: Exp Asymptotic Saturation, Exp Power Base Saturation, Exp Geometric Growth Base, Exp Growth Standard, Exp Log Linear Form, Exp Biphasic Association, Exp Single Phase Growth, Exp Dual Phase Growth, Exp Triple Phase Growth, Exp Delayed Onset Growth.

    Returns:
        list[list]: A 2D list where the first row contains parameter names, the second row contains fitted parameter values, and an optional third row contains standard errors (when covariance estimation succeeds). Returns an error string on failure.
    """
    def _validate_data(xdata, ydata):
        """Validate and convert both xdata and ydata to numpy arrays."""
        x_vals = []
        y_vals = []

        for name, arg, vals_list in [("xdata", xdata, x_vals), ("ydata", ydata, y_vals)]:
            if not isinstance(arg, list) or len(arg) < 2:
                raise ValueError(f"{name} must be a 2D list with at least two rows")
            for i, row in enumerate(arg):
                if not isinstance(row, list) or len(row) == 0:
                    raise ValueError(f"{name} row {i} must be a non-empty list")
                try:
                    vals_list.append(float(row[0]))
                except (ValueError, TypeError, IndexError) as e:
                    raise ValueError(f"{name} row {i} contains non-numeric value")

        x_arr = np.asarray(x_vals, dtype=np.float64)
        y_arr = np.asarray(y_vals, dtype=np.float64)

        if x_arr.shape[0] != y_arr.shape[0]:
            raise ValueError("xdata and ydata must have the same number of rows")
        return x_arr, y_arr

    # Model definitions dictionary
    models = {
        'exp_asymptotic_saturation': {
            'params': ['A'],
            'model': lambda x, A: 1.0 - np.exp(-A * x),
            'guess': lambda xa, ya: (1.0,),
            'bounds': (0.0, np.inf),
        },
        'exp_power_base_saturation': {
            'params': ['B'],
            'model': lambda x, B: 1.0 - np.power(B, x),
            'guess': lambda xa, ya: (0.5,),
            'bounds': (0.0, 1.0),
        },
        'exp_geometric_growth_base': {
            'params': ['a', 'b'],
            'model': lambda x, a, b: a * np.power(b, x),
            'guess': lambda xa, ya: (float(np.max(ya) if len(ya) > 0 and np.max(ya) != 0 else 1.0), 1.1),
            'bounds': (0.0, np.inf),
        },
        'exp_growth_standard': {
            'params': ['a', 'b'],
            'model': lambda x, a, b: a * np.exp(b * x),
            'guess': lambda xa, ya: (float(np.max(ya) if len(ya) > 0 and np.max(ya) != 0 else 1.0), 0.1),
            'bounds': ([0.0, -np.inf], np.inf),
        },
        'exp_log_linear_form': {
            'params': ['a', 'b'],
            'model': lambda x, a, b: np.exp(a + b * x),
            'guess': lambda xa, ya: (float(np.log(np.max(ya)) if len(ya) > 0 and np.all(ya > 0) and np.max(ya) > 0 else 0.0), 0.1),
        },
        'exp_biphasic_association': {
            'params': ['y0', 'A1', 't1', 'A2', 't2'],
            'model': lambda x, y0, A1, t1, A2, t2: y0 + A1 * (1 - np.exp(-x / t1)) + A2 * (1 - np.exp(-x / t2)),
            'guess': lambda xa, ya: (float(np.min(ya)), float(np.ptp(ya) / 2 if np.ptp(ya) != 0 else 0.5), 1.0, float(np.ptp(ya) / 2 if np.ptp(ya) != 0 else 0.5), 5.0),
            'bounds': ([-np.inf, -np.inf, 0.0, -np.inf, 0.0], np.inf),
        },
        'exp_single_phase_growth': {
            'params': ['y0', 'A1', 't1'],
            'model': lambda x, y0, A1, t1: y0 + A1 * np.exp(x / t1),
            'guess': lambda xa, ya: (float(np.min(ya)), float(np.ptp(ya) if np.ptp(ya) != 0 else 1.0), 1.0),
        },
        'exp_dual_phase_growth': {
            'params': ['y0', 'A1', 't1', 'A2', 't2'],
            'model': lambda x, y0, A1, t1, A2, t2: y0 + A1 * np.exp(x / t1) + A2 * np.exp(x / t2),
            'guess': lambda xa, ya: (float(np.min(ya)), float(np.ptp(ya) if np.ptp(ya) != 0 else 1.0), 1.0, float(np.ptp(ya) if np.ptp(ya) != 0 else 0.5), 5.0),
        },
        'exp_triple_phase_growth': {
            'params': ['y0', 'A1', 't1', 'A2', 't2', 'A3', 't3'],
            'model': lambda x, y0, A1, t1, A2, t2, A3, t3: y0 + A1 * np.exp(x / t1) + A2 * np.exp(x / t2) + A3 * np.exp(x / t3),
            'guess': lambda xa, ya: (float(np.min(ya)), float(np.ptp(ya) if np.ptp(ya) != 0 else 1.0), 0.5, float(np.ptp(ya) if np.ptp(ya) != 0 else 0.5), 2.0, float(np.ptp(ya) if np.ptp(ya) != 0 else 0.25), 5.0),
        },
        'exp_delayed_onset_growth': {
            'params': ['y0', 'x0', 'A1', 't1'],
            'model': lambda x, y0, x0, A1, t1: y0 + A1 * np.exp((x - x0) / t1),
            'guess': lambda xa, ya: (float(np.min(ya)), float(np.min(xa)), float(np.ptp(ya) if np.ptp(ya) != 0 else 1.0), 1.0),
            'bounds': ([-np.inf, -np.inf, -np.inf, 0.0], np.inf),
        }
    }

    # Validate and normalize model parameter
    if not isinstance(exp_growth_model, str):
        return "exp_growth_model must be a string"

    exp_growth_model = exp_growth_model.strip()

    if exp_growth_model not in models:
        return f"Invalid model: '{exp_growth_model}'. Valid models are: {', '.join(sorted(models.keys()))}"

    model_info = models[exp_growth_model]

    # Validate and convert input data
    try:
        x_arr, y_arr = _validate_data(xdata, ydata)
    except ValueError as e:
        return f"Data validation error: {str(e)}"

    # Perform curve fitting
    try:
        p0 = model_info['guess'](x_arr, y_arr)
        bounds = model_info.get('bounds', (-np.inf, np.inf))

        # Call curve_fit with or without bounds
        if 'bounds' in model_info:
            popt, pcov = scipy_curve_fit(model_info['model'], x_arr, y_arr, p0=p0, bounds=bounds, maxfev=10000)
        else:
            popt, pcov = scipy_curve_fit(model_info['model'], x_arr, y_arr, p0=p0, maxfev=10000)

        # Convert fitted parameters to float and validate
        fitted_vals = [float(v) for v in popt]
        for v in fitted_vals:
            if math.isnan(v) or math.isinf(v):
                return "Fitting produced invalid numeric values (NaN or inf)"
    except ValueError as e:
        return f"Parameter estimation error: {str(e)}"
    except RuntimeError as e:
        return f"Curve fitting failed to converge: {str(e)}"
    except Exception as e:
        return f"Curve fitting error: {str(e)}"

    # Calculate standard errors
    std_errors = None
    try:
        if pcov is not None and np.isfinite(pcov).all():
            std_errors = [float(v) for v in np.sqrt(np.diag(pcov))]
    except Exception:
        pass

    return [model_info['params'], fitted_vals, std_errors] if std_errors else [model_info['params'], fitted_vals]

Online Calculator